﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO.Ports;
using System.Reflection;
using System.ComponentModel;
using System.Threading;
using System.IO;
using System.Windows.Forms;

namespace PMCSerialTransfer
{

    public class SerialPortManager : IDisposable
    {
        public SerialPortManager(SerialSettings settings)
        {
            // Finding installed serial ports on hardware
            _currentSerialSettings.PortNameCollection = SerialPort.GetPortNames();
            _currentSerialSettings.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(_currentSerialSettings_PropertyChanged);

            _currentSerialSettings.BaudRate = settings.BaudRate;
            _currentSerialSettings.PortName = settings.PortName;
            // If serial ports is found, we select the first found
            //if (_currentSerialSettings.PortNameCollection.Length > 0)
            //    _currentSerialSettings.PortName = _currentSerialSettings.PortNameCollection[0];
        }


        ~SerialPortManager()
        {
            Dispose(false);
        }


        #region Fields
        private SerialPort _serialPort;
        private SerialSettings _currentSerialSettings = new SerialSettings();
        private string _latestRecieved = String.Empty;
        public event EventHandler<SerialDataEventArgs> NewSerialDataReceived;

        #endregion

        #region Properties
        /// <summary>
        /// Gets or sets the current serial port settings
        /// </summary>
        public SerialSettings CurrentSerialSettings
        {
            get { return _currentSerialSettings; }
            set { _currentSerialSettings = value; }
        }

        #endregion

        #region Event handlers

        void _currentSerialSettings_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            // if serial port is changed, a new baud query is issued
            if (e.PropertyName.Equals("PortName"))
                UpdateBaudRateCollection();
        }


        void _serialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            //int dataLength = _serialPort.BytesToRead;
            int dataLength;
            byte[] packetlen = new byte[1];
            byte[] data = new byte[1];

            int nbrDataRead = 0;
            try
            {
                nbrDataRead = _serialPort.Read(packetlen, 0, 1);

                if (nbrDataRead == 0)
                    return;
                dataLength = (int)packetlen[0] - 1;
               
                while (_serialPort.BytesToRead < dataLength) { }

                data = new byte[dataLength + 1];
                if (dataLength == -1)
                    return;
                data[0] = packetlen[0];

                nbrDataRead = _serialPort.Read(data, 1, dataLength);
                if (nbrDataRead == 0)
                    return;

            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "Serial Port", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }

            // Send data to whom ever interested
            if (NewSerialDataReceived != null)
                NewSerialDataReceived(this, new SerialDataEventArgs(data));
        }

        #endregion

        #region Methods

        /// <summary>
        /// Connects to a serial port defined through the current settings
        /// </summary>
        public void StartListening()
        {
            // Closing serial port if it is open 
            if (_serialPort != null && _serialPort.IsOpen)
                _serialPort.Close();
                _serialPort.DataReceived -= new SerialDataReceivedEventHandler(_serialPort_DataReceived);
            Globals.Message = "";
            // Setting serial port settings
            _serialPort = new SerialPort(
                _currentSerialSettings.PortName,
                _currentSerialSettings.BaudRate,
                _currentSerialSettings.Parity,
                _currentSerialSettings.DataBits,
                _currentSerialSettings.StopBits);
            _serialPort.WriteTimeout = 10000;
            _serialPort.ReadTimeout = 10000;

            // Subscribe to event and open serial port for data
            _serialPort.DataReceived += new SerialDataReceivedEventHandler(_serialPort_DataReceived);
            try
            {
                _serialPort.Open();
            }
            catch (IOException ex)
            {
                Globals.Message = ex.Message;
            }

        }

        public void WriteData(byte[] data)
        {
            if (_serialPort.IsOpen)
                _serialPort.Write(data, 0, data.Length);
        }

        /// <summary>
        /// Closes the serial port
        /// </summary>
        public void StopListening()
        {
            _serialPort.Close();
            _serialPort.DataReceived -= new SerialDataReceivedEventHandler(_serialPort_DataReceived);
        }


        /// <summary>
        /// Retrieves the current selected device's COMMPROP structure, and extracts the dwSettableBaud property
        /// </summary>
        private void UpdateBaudRateCollection()
        {
            _serialPort = new SerialPort(_currentSerialSettings.PortName);
            try
            {
                _serialPort.Open();
            }
            catch (IOException ex)
            {
                Globals.Message = ex.Message;
                return;
            }
            object p = _serialPort.BaseStream.GetType().GetField("commProp", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(_serialPort.BaseStream);
            Int32 dwSettableBaud = (Int32)p.GetType().GetField("dwSettableBaud", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public).GetValue(p);

            _serialPort.Close();
            _currentSerialSettings.UpdateBaudRateCollection(dwSettableBaud);
        }

        // Call to release serial port
        public void Dispose()
        {
            Dispose(true);
        }

        // Part of basic design pattern for implementing Dispose
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                _serialPort.DataReceived -= new SerialDataReceivedEventHandler(_serialPort_DataReceived);
            }
            // Releasing serial port (and other unmanaged objects)
            if (_serialPort != null)
            {
                if (_serialPort.IsOpen)
                    _serialPort.Close();

                _serialPort.Dispose();
            }
        }

        public bool IsOpen()
        {
            return _serialPort.IsOpen;
        }


        public void Reset()
        {
            _serialPort.DataReceived -= new SerialDataReceivedEventHandler(_serialPort_DataReceived);
            if (!_serialPort.IsOpen)
                StartListening();

            _serialPort.DtrEnable = true;
            System.Threading.Thread.Sleep(100);

            _serialPort.DtrEnable = false;

            System.Threading.Thread.Sleep(1000);
            StopListening();
            System.Threading.Thread.Sleep(100);
            StartListening();

        }

        #endregion

    }

    /// <summary>
    /// EventArgs used to send bytes recieved on serial port
    /// </summary>
    public class SerialDataEventArgs : EventArgs
    {
        public SerialDataEventArgs(byte[] dataInByteArray)
        {
            Data = dataInByteArray;
        }

        /// <summary>
        /// Byte array containing data from serial port
        /// </summary>
        public byte[] Data;
    }
}


